package com.chinasofti.aspect;

import com.chinasofti.annotation.RedisCache;
import com.chinasofti.annotation.RedisEvict;
import com.chinasofti.util.FastJsonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;


@Aspect
@Component
@SuppressWarnings(value = {"rawtypes", "unchecked"})
public class RedisAspect {

    private static final String DELIMITER = "|";
    @Autowired
    @Qualifier("stringRedisTemplate")
    private StringRedisTemplate stringRedisTemplate ;
    /**
     * Service层切点 使用到了我们定义的 RedisCache 作为切点表达式。
     * 而且我们可以看出此表达式基于 annotation。
     * 并且用于内建属性为查询的方法之上
     */
    @Pointcut("@annotation(com.chinasofti.annotation.RedisCache)")
    public void redisCacheAspect() {
    }

    /**
     * Service层切点 使用到了我们定义的 RedisEvict 作为切点表达式。
     * 而且我们可以看出此表达式是基于 annotation 的。
     * 并且用于内建属性为非查询的方法之上，用于更新表
     */
    @Pointcut("@annotation(com.chinasofti.annotation.RedisEvict)")
    public void redisCacheEvict() {
    }


    @Around("redisCacheAspect()")
    public Object around(ProceedingJoinPoint joinPoint){

        // 得到类名、方法名和参数
        String clazzName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        // 根据类名、方法名和参数生成Key
        System.out.println("key参数: " + clazzName + "." + methodName);
        String key = getKey(clazzName, methodName, args);

        // 得到被代理的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        Annotation[] annotations = method.getAnnotations();
        System.out.println("size:"+annotations.length) ;
        for (Annotation annotation : annotations) {
            // 获取注解的具体类型
            Class<? extends Annotation> annotationType = annotation.annotationType();
            System.out.println(method.getName()+"()\t" + annotationType.getName());
        }


        // 得到被代理的方法上的注解

        Class modelType = method.getAnnotation(RedisCache.class).type();

        // 检查Redis中是否有缓存
        String value = (String) stringRedisTemplate.opsForHash().get(modelType.getName(), key);

        // 得到被代理方法的返回值类型
        Class returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();

        // result是方法的最终返回结果
        Object result = null;
        try {
            if (null == value) {
                // 调用数据库查询方法
                result = joinPoint.proceed(args);

                // 序列化查询结果
                String json = FastJsonUtil.toJsonString(result);
                System.out.println("没命中："+json);

                // 序列化结果放入缓存
                stringRedisTemplate.opsForHash().put(modelType.getName(), key, json);
            } else {
                result = value;
                // 反序列化 从缓存中拿到的json字符串
                result = FastJsonUtil.toObject(value, returnType);
                //result = GsonUtil.fromJson(value,returnType);
                System.out.println("命中："+result.toString());
            }
        } catch (Throwable e) {

        }
        return result;
    }

    @Around("redisCacheEvict()")
    public Object evictCache(ProceedingJoinPoint joinPoint) throws Throwable {
         System.out.println("清空缓存#################") ;
        //得到被代理的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //得到被代理的方法上的注解
        Class modelType = method.getAnnotation(RedisEvict.class).type();
        //清除对应缓存
        stringRedisTemplate.delete(modelType.getName());
        return joinPoint.proceed(joinPoint.getArgs());
    }

    /**
     *      * 根据类名、方法名和参数生成Key
     *      * @param clazzName
     *      * @param methodName
     *      * @param args
     *      * @return key格式：全类名|方法名｜参数类型
     *
     */
    private String getKey(String clazzName, String methodName, Object[] args) {
        StringBuilder key = new StringBuilder(clazzName);
        key.append(DELIMITER);
        key.append(methodName);
        key.append(DELIMITER);
        for (Object obj : args) {
            key.append(obj.getClass().getSimpleName());
            key.append(DELIMITER);
        }
        return key.toString();
    }
}

